/* 
 * FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
 * Copyright (C) 2005/2006, Anthony Minessale II <anthmct@yahoo.com>
 *
 * Version: MPL 1.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
 *
 * The Initial Developer of the Original Code is
 * Anthony Minessale II <anthmct@yahoo.com>
 * Portions created by the Initial Developer are Copyright (C)
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 * 
 * Anthony Minessale II <anthmct@yahoo.com>
 * Neal Horman <neal at wanlink dot com>
 * Seven Du <seven at idapted dot com>
 *
 * mod_recpld.c -- Record raw payload, primarily for g729, other codecs not tested!
 *
 */
#include "recpld.h"

//SWITCH_MODULE_SHUTDOWN_FUNCTION(mod_recpld_shutdown);
//SWITCH_MODULE_RUNTIME_FUNCTION(mod_recpld_runtime);

SWITCH_MODULE_LOAD_FUNCTION(mod_recpld_load);
SWITCH_MODULE_DEFINITION(mod_recpld, mod_recpld_load, NULL, NULL);


/*
  Called when the system shuts down
SWITCH_MODULE_SHUTDOWN_FUNCTION(mod_recpld_shutdown);
{
        return SWITCH_STATUS_SUCCESS;
}
*/

/*
  If it exists, this is called in it's own thread when the module-load completes
  If it returns anything but SWITCH_STATUS_TERM it will be called again automaticly
SWITCH_MODULE_RUNTIME_FUNCTION(mod_recpld_runtime);
{
        while(looping)
        {
                switch_yield(1000);
        }
        return SWITCH_STATUS_TERM;
}
*/

#define FILE_STARTSAMPLES 1024 * 32
#define FILE_BLOCKSIZE 1024 * 8
#define FILE_BUFSIZE 1024 * 64

SWITCH_DECLARE(switch_status_t) switch_payload_play_file(switch_core_session_t *session, switch_file_handle_t *fh, const char *file, switch_input_args_t *args){
        switch_channel_t *channel = switch_core_session_get_channel(session);
        int16_t *abuf = NULL;
        switch_dtmf_t dtmf = { 0 };
        uint32_t interval = 0, samples = 0, framelen; //, sample_start = 0;
        uint32_t ilen = 0;
        // switch_size_t olen = 0, llen = 0;
        switch_frame_t write_frame = { 0 };
        switch_timer_t timer = { 0 };
//      switch_core_thread_session_t thread_session = { 0 };
        switch_codec_t codec;
        switch_memory_pool_t *pool = switch_core_session_get_pool(session);
        // char *codec_name;
        switch_status_t status = SWITCH_STATUS_SUCCESS;
        // switch_file_handle_t lfh;
        switch_codec_t *read_codec = NULL;
        // const char *p;
        uint8_t asis = 0;
        char *ext;
        const char *prefix;
        const char *timer_name;
        // const char *prebuf;
//      const char *alt = NULL;
//      int eof = 0;
        switch_size_t bread = 0;
        int fd;

        switch_channel_pre_answer(channel);

        prefix = switch_channel_get_variable(channel, "sound_prefix");
        timer_name = switch_channel_get_variable(channel, "timer_name");

        read_codec = switch_core_session_get_read_codec(session);

        if (switch_strlen_zero(file) || !read_codec) {
                switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "File name missing!!"); 
                status = SWITCH_STATUS_FALSE;
                goto end;
        }

        if (!prefix) {
                prefix = SWITCH_GLOBAL_dirs.base_dir;
        }

        if (!strstr(file, SWITCH_URL_SEPARATOR)) {
                if (!switch_is_file_path(file)) {
                        file = switch_core_session_sprintf(session, "%s%s%s", prefix, SWITCH_PATH_SEPARATOR, file);
                }
                if ((ext = strrchr(file, '.'))) {
                        ext++;
                } else {
                        ext = read_codec->implementation->iananame;
                        file = switch_core_session_sprintf(session, "%s.%s", file, ext);
                        asis = 1;
                }
        }

        if ((fd = open(file, O_RDONLY)) == -1 ) {
                switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "File Open error[errno:%d]: %s\n", errno, file);
                switch_core_session_reset(session, SWITCH_TRUE, SWITCH_FALSE);
                status = SWITCH_STATUS_NOTFOUND;
                goto end;
        }
    
        switch_zmalloc(abuf, FILE_STARTSAMPLES * sizeof(*abuf));
        write_frame.data = abuf;
        write_frame.buflen = FILE_STARTSAMPLES;

        switch_assert(read_codec != NULL);
        interval = read_codec->implementation->microseconds_per_packet / 1000;

        write_frame.codec = read_codec;
        samples = read_codec->implementation->samples_per_packet;
        framelen = read_codec->implementation->encoded_bytes_per_packet;
        if (timer_name) {
                uint32_t len;

                len = samples * 2;
                if (switch_core_timer_init(&timer, timer_name, interval, samples, pool) != SWITCH_STATUS_SUCCESS) {
                        switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "setup timer failed!\n");
                        switch_core_codec_destroy(&codec);
                        close(fd);
                        switch_core_session_reset(session, SWITCH_TRUE, SWITCH_FALSE);
                        status = SWITCH_STATUS_GENERR;
                        goto end;
                }
                switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "setup timer success %u bytes per %d ms!\n", len, interval);
        }
//      write_frame.rate = fh->samplerate;
        write_frame.rate = 8000;

        if (timer_name) {
                /* start a thread to absorb incoming audio */
                switch_core_service_session(session);
        }

        ilen = samples;
        asis = 1;

        for (;;) {
                int done = 0;
                int do_speed = 1;
//              int last_speed = -1;

                if (!switch_channel_ready(channel)) {
                        status = SWITCH_STATUS_FALSE;
                        break; 
                }

                if (switch_channel_test_flag(channel, CF_BREAK)) {
                        switch_channel_clear_flag(channel, CF_BREAK);
                        status = SWITCH_STATUS_BREAK;
                        break; 
                }

                if (switch_core_session_private_event_count(session)) {
                        switch_ivr_parse_all_events(session);
                }

                if (args && (args->input_callback || args->buf || args->buflen)) {
                        /*
                           dtmf handler function you can hook up to be executed when a digit is dialed during playback 
                           if you return anything but SWITCH_STATUS_SUCCESS the playback will stop.
                         */
                        if (switch_channel_has_dtmf(channel)) {
                                if (!args->input_callback && !args->buf) {
                                        status = SWITCH_STATUS_BREAK;
                                        done = 1;
                                        break; 
                                }
                                switch_channel_dequeue_dtmf(channel, &dtmf);
                                if (args->input_callback) {
                                        status = args->input_callback(session, (void *) &dtmf, SWITCH_INPUT_TYPE_DTMF, args->buf, args->buflen);
                                } else {
                                        *((char *) args->buf) = dtmf.digit;
                                        status = SWITCH_STATUS_BREAK;
                                }
                        }

                        if (args->input_callback) {
                                switch_event_t *event;

                                if (switch_core_session_dequeue_event(session, &event) == SWITCH_STATUS_SUCCESS) {
                                        status = args->input_callback(session, event, SWITCH_INPUT_TYPE_EVENT, args->buf, args->buflen);
                                        switch_event_destroy(&event);
                                }
                        }

                        if (status != SWITCH_STATUS_SUCCESS) {
                                done = 1;
                                break; 
                        }
                }


                if (!(bread = read(fd, abuf, framelen))) {
                                break; 
                }

                if (bread < framelen) {
                        memset(abuf + bread, 0, framelen - bread);
                }

                do_speed = 0;
  
                if (done) {
                        break; 
                }

//              write_frame.samples = (uint32_t) olen;
                write_frame.datalen = bread;

                if (timer_name) {
                        write_frame.timestamp = timer.samplecount;
                }
#ifndef WIN32
#if 0 // don't seem to need it, maybe only on mac?
//#if SWITCH_BYTE_ORDER == __BIG_ENDIAN
                if (!asis) {
                        switch_swap_linear(write_frame.data, (int) write_frame.datalen / 2);
                }
#endif
#endif

                status = switch_core_session_write_frame(session, &write_frame, SWITCH_IO_FLAG_NONE, 0);

                if (status == SWITCH_STATUS_MORE_DATA) {
                        status = SWITCH_STATUS_SUCCESS;
                        continue;
                } else if (status != SWITCH_STATUS_SUCCESS) {
                        done = 1;
                        break; 
                }

                if (done) {
                        break; 
                }

                if (timer_name) {
                        if (switch_core_timer_next(&timer) != SWITCH_STATUS_SUCCESS) {
                                break; 
                        }
                } else {                                /* time off the channel (if you must) */
                        switch_frame_t *read_frame;
                        switch_status_t tstatus;
                        while (switch_channel_ready(channel) && switch_channel_test_flag(channel, CF_HOLD)) {
                                switch_yield(10000);
                        }

                        tstatus = switch_core_session_read_frame(session, &read_frame, SWITCH_IO_FLAG_NONE, 0);

                        if (!SWITCH_READ_ACCEPTABLE(tstatus)) {
                                break; 
                        }

                        if (args && (args->read_frame_callback)) {
                                int ok = 1;
                                switch_set_flag(fh, SWITCH_FILE_CALLBACK);
                                if (args->read_frame_callback(session, read_frame, args->user_data) != SWITCH_STATUS_SUCCESS) {
                                        ok = 0;
                                }
                                switch_clear_flag(fh, SWITCH_FILE_CALLBACK);
                                if (!ok) {
                                        break; 
                                }
                        }
                }
        }

        switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "done playing payload file:%s\n", file);

        close(fd);

        if (timer_name) {
                /* End the audio absorbing thread */
                switch_core_thread_session_end(session);
                switch_core_timer_destroy(&timer);
        }


  end:
        switch_safe_free(abuf);
        switch_core_session_reset(session, SWITCH_TRUE, SWITCH_FALSE);
        return status;
}

static switch_bool_t record_payload_callback(switch_media_bug_t *bug, void *user_data, switch_abc_type_t type)
{
        payload_recording_file_handler_t *recording_fds = (payload_recording_file_handler_t *)user_data;
        uint8_t rdata[SWITCH_RECOMMENDED_BUFFER_SIZE];
        uint8_t wdata[SWITCH_RECOMMENDED_BUFFER_SIZE];
        size_t rlen = 0, wlen = 0, rdlen = 0, maxlen = 0;
        size_t rdatalen = 0, wdatalen = 0;
        int i;

        switch (type) {
        case SWITCH_ABC_TYPE_INIT:
                break;
        case SWITCH_ABC_TYPE_CLOSE:
                if (recording_fds->r_fd > 0 ) {
                        close(recording_fds->r_fd);
                        switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "A-leg recording file closed\n"); 
                }
                if (recording_fds->w_fd > 0 ) {
                        close(recording_fds->w_fd);
                        switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "B-leg recording file closed\n");
                }

                switch_safe_free(recording_fds);

                break;
        case SWITCH_ABC_TYPE_READ:
                if (recording_fds->r_fd && recording_fds->w_fd) {
                //      switch_size_t len;
                        switch_core_session_t *session = switch_core_media_bug_get_session(bug);
                        switch_channel_t *channel = switch_core_session_get_channel(session);
            
                        if (bug->raw_read_buffer) {
                                rlen = switch_buffer_inuse(bug->raw_read_buffer);
                        }
                        if (bug->raw_write_buffer) {
                                wlen = switch_buffer_inuse(bug->raw_write_buffer);
                        }
                        if ((bug->raw_read_buffer && bug->raw_write_buffer) && (!rlen && !wlen)) {
                                break;
                        }

                        maxlen = SWITCH_RECOMMENDED_BUFFER_SIZE;

                        rlen = rlen >  SWITCH_RECOMMENDED_BUFFER_SIZE ?  SWITCH_RECOMMENDED_BUFFER_SIZE : rlen;
                        wlen = wlen >  SWITCH_RECOMMENDED_BUFFER_SIZE ?  SWITCH_RECOMMENDED_BUFFER_SIZE : wlen;

                        if ((rdlen = rlen > wlen ? wlen : rlen) > maxlen) {
                                rdlen = maxlen;
                        }

                        if (!rdlen) {
                                rdlen = rlen > wlen ? rlen : wlen;
                        }

                        if (rlen) {
                                switch_mutex_lock(bug->read_mutex);
                                rdatalen = (uint32_t) switch_buffer_read(bug->raw_read_buffer, rdata, rdlen);
                                switch_mutex_unlock(bug->read_mutex);
                        }
                        if (wlen) {
                                switch_mutex_lock(bug->write_mutex);
                                wdatalen = (uint32_t) switch_buffer_read(bug->raw_write_buffer, wdata, rdlen);
                                switch_mutex_unlock(bug->write_mutex);  
                        }

                        //align data in both directions
                        rdlen = rdatalen > wdatalen ? rdatalen : wdatalen;

                        //fill 0's if one leg loss packets, stop when we meet 4 bytes of 0xff
                        for (i=0; i<rdlen && i<rdatalen; i+= 10) {
                                if ( ( *((int32_t *)(rdata + i)) ^ 0xffffffff) == 0 ) {
                                        rlen = i;
                                        break;
                                }
                        }

                        rlen = rlen > rdatalen ? rdatalen : rlen;
                        if ( rlen < rdlen ) {
                                memset(rdata + rlen, 0, rdlen - rlen);
                        }

                        for (i=0; i<rdlen && i<wdatalen; i+= 10) {
                                if ( ( *((int32_t *)(wdata + i)) ^ 0xffffffff) == 0 ) {
                                        wlen = i;
                                        break;
                                }
                        }
                        wlen = wlen > wdatalen ? wdatalen : wlen;
                        if (wlen < rdlen) {
                                memset(wdata + wlen, 0, rdlen - wlen);
                        }

                        rdlen = rlen > wlen ? rlen : wlen;

                        if (rdlen) {
                                int doit = 1;
                                if (!switch_channel_test_flag(channel, CF_ANSWERED) && switch_core_media_bug_test_flag(bug, SMBF_RECORD_ANSWER_REQ)) {
                                        doit = 0;
                                }
                                if (doit) {
                                         
                                        write(recording_fds->r_fd, rdata, (switch_size_t)rdlen );
                                        write(recording_fds->w_fd, wdata, (switch_size_t)rdlen );

                                }
                        }
                }
                break;
        case SWITCH_ABC_TYPE_WRITE:
        default:
                break;
        }

        return SWITCH_TRUE;
}

SWITCH_DECLARE(switch_status_t) switch_record_session_payload(switch_core_session_t *session, char *file, uint32_t limit)
{
        switch_channel_t *channel = switch_core_session_get_channel(session);
        switch_codec_t *read_codec = switch_core_session_get_read_codec(session);
    const char *p;
        // const char *vval;
        switch_media_bug_t *bug;
        switch_status_t status;
        time_t to = 0;
        switch_media_bug_flag_t flags = SMBF_READ_STREAM | SMBF_WRITE_STREAM;
        uint8_t channels = read_codec->implementation->number_of_channels;
        //uint32_t samplerate = read_codec->implementation->actual_samples_per_second;
        char pr_filename[1024];
        char pw_filename[1024];
        char *uuid = switch_core_session_get_uuid(session);
        char *codec_name = read_codec->implementation->iananame;
        payload_recording_file_handler_t *recording_fds;

        switch_zmalloc(recording_fds, sizeof(payload_recording_file_handler_t) );

        sprintf(pr_filename, "%s-r.%s", file, codec_name);
        sprintf(pw_filename, "%s-w.%s", file, codec_name);

        if ((bug = switch_channel_get_private(channel, uuid))) {
                return switch_stop_record_session_payload(session, uuid);
        }
        switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_NOTICE, "Record payload to file: %s\n", file);

        if ((p = switch_channel_get_variable(channel, "RECORD_STEREO")) && switch_true(p)) {
                        flags |= SMBF_STEREO;
                        channels = 2;
                }

        if ((p = switch_channel_get_variable(channel, "RECORD_ANSWER_REQ")) && switch_true(p)) {
                flags |= SMBF_RECORD_ANSWER_REQ;
         }
        if ((recording_fds->r_fd = open(pr_filename, O_WRONLY | O_CREAT, 0644) ) < 1 || 
                (recording_fds->w_fd = open(pw_filename, O_WRONLY | O_CREAT, 0644) ) < 1 ) {
                switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Error opening %s or %s\n", pr_filename, pw_filename);
                //switch_channel_hangup(channel, SWITCH_CAUSE_DESTINATION_OUT_OF_ORDER);
                //switch_core_session_reset(session, SWITCH_TRUE);
                if (recording_fds->r_fd > 0) close(recording_fds->r_fd);
                if (recording_fds->w_fd > 0) close(recording_fds->w_fd);
                switch_safe_free(recording_fds);
                return SWITCH_STATUS_GENERR;
        } 
//      switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_NOTICE, "==Recording to #:%d\n", *rp_fd);

        switch_channel_pre_answer(channel);


        if (limit) {
                to = switch_timestamp(NULL) + limit;
        }

        if ((status = switch_core_media_bug_add(session, record_payload_callback, recording_fds, to, flags, &bug)) != SWITCH_STATUS_SUCCESS) {
                switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Error adding media bug for file %s\n", pr_filename);
                close(recording_fds->r_fd);
                close(recording_fds->w_fd);
                switch_safe_free(recording_fds);
                return status;
        }

        switch_channel_set_private(channel, uuid, bug);

        return SWITCH_STATUS_SUCCESS;
}

SWITCH_DECLARE(switch_status_t) switch_stop_record_session_payload(switch_core_session_t *session, const char *file)
{
        switch_media_bug_t *bug;
        switch_channel_t *channel = switch_core_session_get_channel(session);
        char *uuid = switch_core_session_get_uuid(session);
    
    switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_NOTICE, "Record Payload Stoped: %s\n", uuid);
        if ((bug = switch_channel_get_private(channel, uuid))) {
                switch_channel_set_private(channel, uuid, NULL);
                switch_core_media_bug_remove(session, &bug);
                return SWITCH_STATUS_SUCCESS;
        }
        return SWITCH_STATUS_FALSE;
} 

#define SESS_REC_PAYLOAD_SYNTAX "<uuid> [start|stop] <path> [<limit>]"
SWITCH_STANDARD_API(session_record_payload_function)
{
        switch_core_session_t *rsession = NULL;
        char *mycmd = NULL, *argv[4] = { 0 };
        char *uuid = NULL, *action = NULL, *path = NULL;
        int argc = 0;
        uint32_t limit = 0;

        if (session) {
                return SWITCH_STATUS_FALSE;
        }

        if (switch_strlen_zero(cmd)) {
                goto usage;
        }

        if (!(mycmd = strdup(cmd))) {
                goto usage;
        }

        if ((argc = switch_separate_string(mycmd, ' ', argv, (sizeof(argv) / sizeof(argv[0])))) < 3) {
                goto usage;
        }

        uuid = argv[0];
        action = argv[1];
        path = argv[2];
        limit = argv[3] ? atoi(argv[3]) : 0;

        if (switch_strlen_zero(uuid) || switch_strlen_zero(action) || switch_strlen_zero(path)) {
                goto usage;
        }

        if (!(rsession = switch_core_session_locate(uuid))) {
                stream->write_function(stream, "-ERR Cannot locate session!\n");
                return SWITCH_STATUS_SUCCESS;
        }

        if (!strcasecmp(action, "start")) {
                switch_record_session_payload(rsession, path, limit);
        } else if (!strcasecmp(action, "stop")) {
                switch_stop_record_session_payload(rsession, path);
        } else {
                goto usage;
        }

        goto done;

  usage:
        stream->write_function(stream, "-USAGE: %s\n", SESS_REC_PAYLOAD_SYNTAX);
        switch_safe_free(mycmd);

  done:
        if (rsession) {
                switch_core_session_rwunlock(rsession);
        }

        switch_safe_free(mycmd);
        return SWITCH_STATUS_SUCCESS;
}

SWITCH_STANDARD_APP(play_payload_function)
{
        switch_input_args_t args = { 0 };

        switch_channel_pre_answer(switch_core_session_get_channel(session));

//      args.input_callback = on_dtmf;
        switch_payload_play_file(session, NULL, data, &args);
} 

SWITCH_STANDARD_APP(record_payload_function)
{
        switch_channel_t *channel = switch_core_session_get_channel(session);
        switch_status_t status;
        uint32_t limit = 0;
        char *path;
//      switch_input_args_t args = { 0 };
//      switch_file_handle_t fh = { 0 };
        int argc;
        char *mydata, *argv[4] = { 0 };
        char *l = NULL;
        const char *tmp;
        int rate;

        if (!switch_strlen_zero(data) && (mydata = switch_core_session_strdup(session, data))) {
                argc = switch_separate_string(mydata, ' ', argv, (sizeof(argv) / sizeof(argv[0])));
        } else {
                switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "No file specified.\n");
                return;
        }

        path = argv[0];
        l = argv[1];

        if (l) {
                if (*l == '+') {
                        l++;
                }
                if (l) {
                        limit = atoi(l);
                        if (limit < 0) {
                                limit = 0;
                        }
                }
        }

        if ((tmp = switch_channel_get_variable(channel, "record_rate"))) {
                rate = atoi(tmp);
        }

//      args.input_callback = on_dtmf;
        status = switch_record_session_payload(session, path, limit);

        if (!switch_channel_ready(channel) || (status != SWITCH_STATUS_SUCCESS && !SWITCH_STATUS_IS_BREAK(status))) {
                switch_channel_hangup(channel, SWITCH_CAUSE_DESTINATION_OUT_OF_ORDER);
        }
}

SWITCH_MODULE_LOAD_FUNCTION(mod_recpld_load)
{
        switch_application_interface_t *app_interface;
        switch_api_interface_t *commands_api_interface;  

        /* connect my internal structure to the blank pointer passed to me */
        *module_interface = switch_loadable_module_create_module_interface(pool, modname);

        switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_NOTICE, "mod_recpld loaded!\n");

        SWITCH_ADD_APP(app_interface, "play_payload", "Playback Payload File", "Playback a payload file to the channel", play_payload_function, "<path>", SAF_NONE);
        SWITCH_ADD_APP(app_interface, "record_payload", "Record Payload to a File", "Record payload to a file", record_payload_function, "<path>", SAF_NONE);
        SWITCH_ADD_API(commands_api_interface, "uuid_record_payload", "session record payload", session_record_payload_function, SESS_REC_PAYLOAD_SYNTAX);


        /* indicate that the module should continue to be loaded */
        return SWITCH_STATUS_SUCCESS;

}

/* For Emacs:
 * Local Variables:
 * mode:c
 * indent-tabs-mode:t
 * tab-width:4
 * c-basic-offset:4
 * End:
 * For VIM:
 * vim:set softtabstop=4 shiftwidth=4 tabstop=4 expandtab:
 */

